/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string>
#include <memory>

#include "yaffut.h"
#include "myassert.h"
#include "TestSession.h"
#include "TestRoom.h"
#include "TestDatabase.h"
#include "TestActivity.h"
#include "SqliteDatabase.h"

#include "Error.h"
#include "MudItem.h"
#include "TaskQueue.h"
#include "Global.h"
#include "MudCredits.h"
#include "ItemDb.h"

#define protected public
#define private public
#include "MudPlayer.h"
#undef private
#undef protected
#include "MudWearable.h"

using namespace std;
using namespace Chronos;

class TestItem : public MudItem {
public:
    TestItem(const std::string& name, bool* destroyed_) : MudItem(0, name, "descr", 5), destroyed(destroyed_) {}
    virtual ~TestItem() { *destroyed = true; }

    bool* destroyed; 
};


struct MudPlayerTest {
    MudPlayerTest()
        : taskqueue(0)
        , session("player")
        , session2("player2")
        , player(session.getPlayer())
        , player2(session2.getPlayer())
        , room("room1")
        , room2("room2")
    {
        Global<TaskQueue>::set(&taskqueue);
        room.addPlayer(player, "");
        room.addPlayer(player2, "");
        session.clear();
        session2.clear();
    }
    ~MudPlayerTest() {
        Room* playerRoom = player.getRoom();
        if (playerRoom != 0) playerRoom->removePlayer(player, "");
        Room* playerRoom2 = player2.getRoom();
        if (playerRoom2 != 0) playerRoom2->removePlayer(player2, "");
    }
    
    TaskQueue taskqueue;
    TestSession session;
    TestSession session2;
    MudPlayer& player;
    MudPlayer& player2;
    TestRoom room;
    TestRoom room2;
};


TEST(MudPlayerTest, testDestruction) {
    bool item1Destroyed = false;
	{
    	TestSession session3("player2");
    	MudPlayer player3(session2, 2, Stats());
    	player3.addItem(new TestItem("testItem1", &item1Destroyed));
	}
    ASSERT_TRUE(item1Destroyed);
}


TEST(MudPlayerTest, testCombatMiss) {
    player.miss(player2);
    session.SESSION_CHECKWRITTEN("player2 misses you.\n");
}


TEST(MudPlayerTest, testTakeDmg) {
    ASSERT_TRUE(player.takeDmg(player2, 20));
    session.SESSION_CHECKWRITTEN("player2 hits you for 20.\n"); 
    ASSERT_EQUAL(80, player.getHealth());

    ASSERT_FALSE(player.takeDmg(player2, 100));
    ASSERT_EQUAL(100, player.getHealth());
    session.SESSION_CHECKWRITTEN("player2 hits you for 100.\nYou die.\nYou are instantly resurrected to full health..now behave yourself this time!\n");
	room.ROOM_CHECKWRITTEN("player is killed by player2.\n");
}


TEST(MudPlayerTest, testDoCombatWithoutTargetInRoom) {
    ASSERT_EQUAL(0, player.doCombat());

    player.addTarget(player2);
    room.removePlayer(player2, "");
    room2.addPlayer(player2, "");
    ASSERT_EQUAL(10, player.doCombat());
}


TEST(MudPlayerTest, testDoCombatMiss) {
    player.addTarget(player2);
    
	player.stats.set(Stats::hitChance, 0);
    ASSERT_EQUAL(10, player.doCombat());
    session.SESSION_CHECKWRITTEN("You miss player2.\n");
	room.ROOM_CHECKWRITTEN("player misses player2.\n");
    session2.SESSION_CHECKWRITTEN("player misses you.\n");
}


TEST(MudPlayerTest, testDoCombatHit) {
    player.addTarget(player2);
	player.stats.set(Stats::hitChance, 100);
	player.stats.set(Stats::maxDamage, 1);
    ASSERT_EQUAL(10, player.doCombat());
    session.SESSION_CHECKWRITTEN("You hit player2 for 1.\n");
	room.ROOM_CHECKWRITTEN("player hits player2 for 1.\n");
    session2.SESSION_CHECKWRITTEN("player hits you for 1.\n");
}


TEST(MudPlayerTest, testFindRemoveItem) {
    MudItem item1(0, "testItem1", "testDescr", 5);
    player.addItem(&item1);
    ASSERT_TRUE(&item1 == player.findItem("testItem1"));
    player.removeItem(&item1);
    ASSERT_NULL(player.findItem("testItem1"));

    try {
        player.removeItem(&item1);
        ASSERT_FAIL();
    } catch(MudError& e) {
        ASSERT_STR_EQUAL("MudPlayer: item testItem1 unknown", e.what());
    }
}


TEST(MudPlayerTest, testFindRemovingEquippedItem) {
    MudWearable* helmet = new MudWearable(0, "helmet", "", 1, Equipment::head);
    player.addItem(helmet);
    player.wear(helmet);

    player.removeItem(helmet);
    ASSERT_FALSE(player.findInInventory("helmet"));
    ASSERT_FALSE(player.findInEquipment("helmet"));
    delete helmet;
}


struct MudPlayerTestWithoutRoom {
    MudPlayerTestWithoutRoom()
        : session("player")
        , player(session.getPlayer())
    {
        session.clear();
    }
    
    TestSession session;
    MudPlayer& player;
};


TEST(MudPlayerTestWithoutRoom, testAddingCredits) {
	ASSERT_EQUAL(0, player.getCredits());
	player.addItem(new MudCredits(0, "money", "", 10));
	ASSERT_EQUAL(10, player.getCredits());
	ASSERT_NULL(player.findItem("money"));

	player.addItem(new MudCredits(0, "money", "", 100));
	ASSERT_EQUAL(110, player.getCredits());
}


struct MudPlayerEquipmentTest {
    MudPlayerEquipmentTest()
        : session("player")
        , player(session.getPlayer())
    {
        session.clear();
    }
    TestSession session;
    MudPlayer& player;
};


TEST(MudPlayerTest, testWearing) {
    MudWearable* helmet = new MudWearable(0, "helmet", "", 1, Equipment::head);
    helmet->addModifier(Stats::maxHealth, 10);
    player.addItem(helmet);
    ASSERT_FALSE(player.findInEquipment("helmet"));
    ASSERT_TRUE(player.findInInventory("helmet"));
    ASSERT_EQUAL(100, player.getMaxHealth());

    player.wear(helmet);
    session.SESSION_CHECKWRITTEN("OK\n");
    ASSERT_TRUE(player.findInEquipment("helmet"));
    ASSERT_FALSE(player.findInInventory("helmet"));
    ASSERT_EQUAL(110, player.getMaxHealth());
	room.ROOM_CHECKWRITTEN("player wears helmet.\n");
}


TEST(MudPlayerTest, testWearingWithSlotTaken) {
    MudWearable* helmet = new MudWearable(0, "helmet", "", 1, Equipment::head);
    helmet->addModifier(Stats::maxHealth, 10);
    player.addItem(helmet);
    player.wear(helmet);
    session.clear();

    MudWearable* helmet2 = new MudWearable(0, "helmet2", "", 1, Equipment::head);
    helmet2->addModifier(Stats::maxHealth, 20);
    player.addItem(helmet2);
    player.wear(helmet2);
    session.SESSION_CHECKWRITTEN("removing helmet.\nOK\n");
    ASSERT_FALSE(player.findInInventory("helmet2"));
    ASSERT_TRUE(player.findInEquipment("helmet2"));
    ASSERT_TRUE(player.findInInventory("helmet"));
    ASSERT_FALSE(player.findInEquipment("helmet"));
    ASSERT_EQUAL(120, player.getMaxHealth());
}


TEST(MudPlayerTest, testUnwearing) {
    MudWearable* helmet = new MudWearable(0, "helmet", "", 1, Equipment::head);
    helmet->addModifier(Stats::maxHealth, 10);
    player.addItem(helmet);
    player.wear(helmet);
    session.clear();
    room.clear();

    player.unwear(helmet);
    session.SESSION_CHECKWRITTEN("removing helmet.\n");
    ASSERT_TRUE(player.findInInventory("helmet"));
    ASSERT_FALSE(player.findInEquipment("helmet"));
    ASSERT_EQUAL(100, player.getMaxHealth());
	room.ROOM_CHECKWRITTEN("player removes helmet.\n");
}


TEST(MudPlayerTest, testNewActivityShouldStopOlder) {
    bool cancelled1 = false;
    bool cancelled2 = false;
    TestActivity* activity1 = new TestActivity(&cancelled1);
    TestActivity* activity2 = new TestActivity(&cancelled2);
    player.setActivity(activity1);

    ASSERT_FALSE(cancelled1);
    player.setActivity(activity2);
    ASSERT_TRUE(cancelled1);

    ASSERT_FALSE(cancelled2);
    player.cancelActivity();
    ASSERT_TRUE(cancelled2);
}


TEST(MudPlayerTest, testDeathStopsActivity) {
    bool cancelled1 = false;
    TestActivity* activity = new TestActivity(&cancelled1);
    player.setActivity(activity);

    ASSERT_FALSE(player.takeDmg(player2, 101));
    ASSERT_TRUE(cancelled1);
}


struct MudPlayerCreation {
    TestDatabase database;
};


TEST(MudPlayerCreation, testPlayerCreation) {
    ASSERT_FALSE(database.hasPlayer("name"));
    MudPlayer::create(database, "name", "password");
    ASSERT_TRUE(database.hasPlayer("name"));
    ASSERT_STR_EQUAL("update players set stats='100,10,10,80,100', credits=0 where name='name';", database.lastQuery);
}


struct MudPlayerInventory {
    MudPlayerInventory()
        : session("player")
        , player(session.getPlayer())
    {
        session.clear();
    	Global<ItemDb>::set(&itemDb);
		MudItem basic(1, "basic", "", 1);
		itemDb.addItem(1, new MudItemFactory(basic));

		MudItem rockProto(2, "rock", "", 1);
		itemDb.addItem(2, new MudItemFactory(rockProto));
    }
    
    TestSession session;
    MudPlayer& player;
	ItemDb itemDb;
};


TEST(MudPlayerInventory, testShouldDeserializeEmpty) {
	player.deserializeInv("");
	const ItemContainer::Items items = player.inventory.getItems();
	ASSERT_TRUE(items.empty());
}


TEST(MudPlayerInventory, testShouldDeserializeEmptyObjecObjecttString) {
	player.deserializeInv(";;;");
	const ItemContainer::Items items = player.inventory.getItems();
	ASSERT_TRUE(items.empty());
}


TEST(MudPlayerInventory, testShouldSerializeOneItem) {
	player.addItem(itemDb.create(1));
	StringBuilder invString;
	player.inventory.serialize(invString);	
	ASSERT_STR_EQUAL("1;", invString);
}


TEST(MudPlayerInventory, testShouldDeserializeOneItem) {
	player.deserializeInv("1;");
	MudItem* item = player.findInInventory("basic");	
	ASSERT_NOT_NULL(item);
}


TEST(MudPlayerInventory, testShouldSerializeMultipleItems) {
	player.addItem(itemDb.create(1));
	player.addItem(itemDb.create(2));
	StringBuilder invString;
	player.inventory.serialize(invString);	
	ASSERT_STR_EQUAL("1;2;", invString);
}


struct MudPlayerSerialization {
    MudPlayerSerialization()
        : database(SqliteDatabase::createDummy("../database.sql"))
        , session("player")
    {
        session.clear();
        MudPlayer::create(*database, "player", "pass");
	}	
	const std::auto_ptr<SqliteDatabase> database; 
    TestSession session;
};


TEST(MudPlayerSerialization, testDeserialization) {
    MudPlayer& player = MudPlayer::load(*database, "player", session);
    ASSERT_EQUAL(1, player.getID());
    ASSERT_EQUAL(0, player.getCredits());
    const Stats& stats = player.getStats();
    ASSERT_EQUAL(10, stats[Stats::speed]);
    ASSERT_EQUAL(100, stats.getHealth());
    delete &player;
}


